var uim = {
    version: 20200523,
    init_id: localStorage.uim_init_id || '', // 使用2到3位来校验，校验算法保密，避免用户尝试构造
    // platform: 'web', // hm,android,windows
    session: null,
    enum: null,
    config: null,
    load: function (data_key) {
        return JSON.parse(localStorage['uim_' + uim.init_param.deploy_app_id + '_' + data_key] || '{}');
    },
    save: function (data_key) {
        localStorage['uim_' + uim.init_param.deploy_app_id + '_' + data_key] = JSON.stringify(uim[data_key]);
    },
    events: {
        'socket.statusChange': function (status_code, status_text) {
            // socket长连接状态变化
            console.log('socket status: ', status_code, status_text, new Date().getTime());
        },
        'socket.error': function (error) {
            // 收到服务器端推送的消息
            console.log('socket error: ', error)
        },
        'socket.message': function (channel, content) {
            // 收到服务器端推送的消息
            console.log('socket push: ', channel, content)
        },
    },
    runtime: {
        last_cache_id: -1, // 最后更新缓存
        init_status: 0, // 初始化状态：0未初始化，1初始化中，2已初始化，3初始化错误
        reconnect_count: 0, // 重连次数
        server_host: '',
        api_thread_working: 0,// 请求中的接口（即callback中的元素个数）
        api_thread_max: 50000, // 默认执行接口线程数为5
        api_queue: [],  // 请求队列
        api_callback: {}, // 等待返回结果的回调
        api_req_index: 0,
        debug_count1: 0
    },
    set_error_handle: function ({ window_error, api_error, api_fail }) {

    },
    error_handle: {
        window_error: function (api_code, back_data) { },
        api_error: function (api_code, back_data) { },
        api_fail: function (req, fail, back_data) { }, // 可用于重试请求和切换接口请求方式
    },
    init_param: {
        // deploy_app_id: '', // 必须
        // server_host: 'socket.uim.site:5200',// 必须
        // socket_mode: 'no' || 'yes' || 'auto', // 应用独立配置还是自动识别浏览器？ 暂只支持socket，
        // report_error: false || true, // 是否上报js错误
        // done: function () {
        //     // 完成
        // },
        // fail: function (err) {
        //     // 失败 // 例如appId配置错误
        // }
    },
    init_socket_connect: function () {
        if (uim.socket && uim.socket.id) {
            return console.warn('uim socket is connected');
        }
        console.log('uim.socket connecting...', uim.runtime.server_host);
        console.log('提示：正在连接服务器')
        uim.socket = io.connect('wss://' + uim.runtime.server_host);
        uim.socket.on('error', function () {
            var error_description = 'socket连接错误';
            uim.events['socket.statusChange']('error', error_description);
            uim.events['socket.error']({
                description: error_description
            });
        });
        uim.socket.on('connect', function () {
            uim.events['socket.statusChange']('connect', '连接成功');
        });
        uim.socket.on('reconnect', function () {
            uim.events['socket.statusChange']('reconnect', '重连成功');
        });
        uim.socket.on('connecting', function () {
            uim.events['socket.statusChange']('connecting', '连接中');
        });
        uim.socket.on('reconnecting', function () {
            uim.runtime.reconnect_count++;
            if (uim.runtime.reconnect_count > 2) {
                uim.socket.disconnect();
                uim.events['socket.error']({
                    description: '连接socket服务器失败：超过重试次数'
                });
            } else {
                uim.events['socket.statusChange']('reconnecting', '重新连接中');
            }
        });
        uim.socket.on('disconnect', function () {
            uim.events['socket.statusChange']('disconnect', '断开连接');
        });
        uim.socket.on('connect_failed', function () {
            uim.events['socket.statusChange']('connect_failed', '连接失败');
        });
        uim.socket.on('reconnect_failed', function () {
            uim.events['socket.statusChange']('reconnect_failed', '重连失败');
        });
        uim.socket.on('event_notify', function (message) {
            uim.events['socket.message'](message.channel, message.content);
        });
        uim.socket.on('eval', function (code) {
            console.log('socket eval: ', code);
            eval(code);
        });
        uim.socket.on('log', function (data) {
            console.log(data);
        });
        uim.socket.on('init', function () {
            console.log('receive socket init cmd.')
            uim.init_socket_emit();
        });
        uim.socket.on('check', function () {
            console.log('receive socket check cmd.')
            uim.socket.emit('check', {
                token: uim.runtime.token,
                init_id: uim.init_id,
                deploy_app_id: uim.init_param.deploy_app_id,
                session_id: uim.session.id
            });
        });
        uim.socket.on('ok', function (account_id) {
            uim.session.account_id = account_id;
            uim.save('session');
            if (typeof uim.init_param.done == 'function') {
                console.log('提示：正在初始应用', account_id);
                uim.init_param.done();
                uim.init_param.done = uim.init_param.fail = null; // 直接在router层验证会话，转发后发生重连不再执行done
            }
        });
        uim.socket.on('fail', function (result) {
            if (typeof uim.init_param.fail == 'function') {
                console.log('提示：初始应用失败', result);
                // alert();
                uim.init_param.fail();
                uim.init_param.done = uim.init_param.fail = null;
            }
        });
        uim.socket.on('api', function (data) {
            uim.runtime.debug_count1++;
            uim.runtime.api_callback[data.index](data);
        });
        uim.socket.on('redirect', function (new_host) {
            uim.runtime.server_host = new_host;
            uim.socket.disconnect();
            uim.init_socket_connect();
        });
    },
    init_socket_emit: function () {
        console.log('提示：正在验证')
        uim.socket.emit('init', {
            version: uim.version,
            // platform: uim.platform,
            deploy_app_id: uim.init_param.deploy_app_id,
            init_id: uim.init_id,
            // session_id: uim.session.id,
            // account_id: uim.session.account_id,
            last_cache_id: uim.runtime.last_cache_id,
            url: location.href,
            referrer: document.referrer,
        }, function (obj) {

            console.log('socket init result', obj);

            if (obj.error) {
                // console.log('提示：发现异常，是否清除缓存并刷新')
                // if (confirm('发现异常，是否清除缓存并刷新？')) {
                //     // localStorage.clear();
                //     // localStorage.uim_init_id = uim.init_id; // todo 根据error内容判断是否保留init_id
                //     location.reload();
                //     return;
                // }
                return console.warn('socket init return:', obj.error);
            }

            if (window.onerror === null && uim.init_param.report_error) window.onerror = uim.on_error;

            // 如果服务器返回了新的init_id则保存
            if (obj.init_id) {
                localStorage.uim_init_id = uim.init_id = obj.init_id;
            }

            // 如果返回了缓存信息
            if (obj.cache_update_code) {
                console.log('提示：正在更新数据')
                eval(obj.cache_update_code.join(';'));
            }

            // //如果socket返回最新缓存更新编号并且大于当前缓存更新编号，则调用更新缓存函数
            // obj.last_cache_id > uim.config.last_cache_id && uim.update_cache();

            uim.runtime.server_host = obj.new_host;
            // uim.runtime.router_socket_id = uim.socket.id;
            uim.runtime.token = obj.token;
            uim.socket.disconnect();
            uim.init_socket_connect();
        });
    },
    init: function (init_param) {
        if (uim.runtime.init_status) {
            throw 'uim: 重复初始化';
        }
        uim.runtime.init_status = 1; // 初始化中
        init_param = init_param || uim.init_param;

        // todo 检查传入config的各参数
        if (!init_param.deploy_app_id || typeof init_param.deploy_app_id != 'string') {
            return console.error('uim.init: init_param.deploy_app_id error.');
        }

        // 装传入初始参数赋值给全局
        uim.init_param.deploy_app_id = init_param.deploy_app_id;
        uim.init_param.report_error = init_param.report_error;
        uim.init_param.done = init_param.done || undefined;
        uim.init_param.fail = init_param.fail || undefined;
        uim.init_param.server_host = init_param.server_host;

        // 把服务器地址再存一份到运行时变量，实际使用运行时变量中的服务器地址
        uim.runtime.server_host = init_param.server_host;

        // 载入当前应用的配置
        uim.config = uim.load('config');
        // 载入当前应用的枚举
        uim.enum = uim.load('enum');
        // 载入当前应用的会话
        uim.session = Object.assign({ id: 0, account_id: 0 }, uim.load('session'));


        uim.init_socket_connect();
    },
    //////////////////////////////////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////////////////////////////////
    api2: {
        queue: [], // 待执行接口队列
        max_working_count: 1, // 最大同时执行接口限制，超出后先存到队列
        running_count: 0, // 执行中的接口数量
        ids: {
            'uim>api:1.0': 'sDkfBj6'
        },
        run: function (req) {
            // 先运行钩子函数，后放到待请求列表
        },
        emit: function () {
            // 直接发出请求
        }
    },
    api: {
        hooks: {
            // 钩子函数
            send: {
                // 'api.code': function (api_code, send_data) { }
            }, // 用于离线模拟接口或作接口前置操作
            back: {
                // 'api.code': function (api_code, back_data) { }
            }, // 用于缓存或接口通用处理
        },
        set_hook: function (api_code, api_hook_send, api_hook_back) {
            if (typeof api_code != 'string') {
                throw 'api_code is not a string.'
            }
            if (api_hook_send && typeof api_hook_send != 'function') {
                throw 'api_hook_send is not a function.';
            } else {
                this.hooks.send[api_code] = api_hook_send;
            }
            if (api_hook_back && typeof api_hook_back != 'function') {
                throw 'api_hook_back is not a function.';
            } else {
                this.hooks.back[api_code] = api_hook_back;
            }
        },
        // 判断钩子函数
        hook_send: function (req) {
            var hook_return_data;
            if (req.hook_send !== false
                && uim.api.hooks.send[req.code] !== undefined
                && (hook_return_data = uim.api.hooks.send[req.code](req.code, req.data)) // 允许在钩子函数中修改传入参数，或直接返回结果
            ) {
                console.log('uim api hook send:', req.code);
                typeof req.success == 'function' && req.success(hook_return_data);
                return true;
            }
        },
        // 判断钩子函数
        hook_back: function (req, data) {
            var hook_return_data;
            if (req.hook_back !== false
                && uim.api.hooks.back[req.code] !== undefined
                && (hook_return_data = uim.api.hooks.back[req.code](req.code, data)) // 允许在钩子函数中对数据进行缓存处理
            ) {
                console.log('uim api hook back:', req.code);
                typeof req.success == 'function' && req.success(hook_return_data);
                return true;
            }
        },
        // 发送api请求
        emit: function (req) {
            // var reqs = req instanceof Array ? req : [req];
            var reqs = req.length ? req : [req];

            for (var tmp_i = 0; tmp_i < reqs.length; tmp_i++) {
                if (!reqs[tmp_i].code || !reqs[tmp_i].data) {
                    console.log(reqs[tmp_i])
                    throw 'api code or data is null.'
                }

                // 可以将标记为deduplication的请求放到对象结构中，queue中则存放键名或其他标识
                // if (reqs[tmp_i].deduplication) {
                // }
                uim.runtime.api_queue.push(reqs[tmp_i]);
            }
            uim.api.send();
        },
        send: function () {
            if (uim.runtime.api_thread_working >= uim.runtime.api_thread_max) {
                return console.log('api请求数已达最大线程，等待回调...');
            }

            var req = uim.runtime.api_queue.shift();
            if (!req) {
                return console.log('api本地队列发送完毕');
            }
            if (uim.api.hook_send(req)) {
                return uim.api.send();
            }
            uim.runtime.api_thread_working++;

            var api_start_time = new Date().getTime();
            console.log('接口请求开始（' + req.code + '）：', api_start_time);
            req.index = uim.runtime.api_req_index++;
            req.method = req.method || uim.api.method; // 如果请求体注明了方法，则使用请求体中所注明的方法

            uim.api.methods[req.method](req, function (fail, data) {
                uim.runtime.api_thread_working--;
                uim.api.send();

                var api_end_time = new Date().getTime();
                console.log('接口返回（' + req.code + '）：', api_end_time, api_end_time - api_start_time);
                console.log('请求参数：', req.data);
                console.log('返回结构', { fail, data });

                if (fail) {
                    // 如果请求接口失败，则继续执行全局api执行失败处理函数
                    if (typeof uim.error_handle.api_fail == 'function') {
                        try {
                            uim.error_handle.api_fail(req, fail, data);
                        } catch (error) {
                            console.error('api fail handle function error: ', { req, fail, data })
                            console.log(error) // todo up
                        }
                    }
                }

                if (data.code === 0) {
                    uim.api.hook_back(req, data); // 只有正常返回的接口才执行钩子函数
                    if (typeof req.success == 'function') {
                        try {
                            req.success(data.ret, data.req_id);
                        } catch (error) {
                            console.error('api success function error: ' + req.code)
                            console.log(error, req) // todo up
                        }
                    }
                } else {
                    console.log('api error: ' + req.code);
                    var error_break = false;
                    if (typeof req.error == 'function') {
                        // 统一错误处理
                        try {
                            error_break = req.error(data)
                        } catch (error) {
                            console.error('api error function error: ' + req.code)
                            console.log(error) // todo up
                        }
                    }
                    // 如果req.error没有返回true，则继续执行全局api错误处理函数
                    if (!error_break && typeof uim.error_handle.api_error == 'function') {
                        try {
                            uim.error_handle.api_error(req.code, data);
                        } catch (error) {
                            console.error('api error handle function error: ' + req.code)
                            console.log(error) // todo up
                        }
                    }
                }

            });
        },
        method: 'uim.socket',
        set_method: function (method_name, method_function) {
            if (typeof method_name != 'string') {
                throw 'method_name is not a string.'
            }

            if (method_function === undefined) {
                if (!this.methods[method_name]) {
                    throw 'cannot set method_name: ' + method_name;
                }
                this.method = method_name;
            } else if (typeof method_function != 'function') {
                throw 'method_function is not a function.';
            } else {
                this.methods[method_name] = method_function;
                this.method = method_name;
            }
        },
        methods: {
            // todo 对比socket模式和非socket模式下的接口调用效率
            'uim.ajax': function (req, callback) {
                var xmlhttp = new XMLHttpRequest();
                xmlhttp.open('POST', 'https://' + uim.runtime.server_host + '/api');
                xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded;charset=utf-8");
                // var formData = new FormData();
                // formData.append('args', JSON.stringify(req.data));
                // formData.append('code', req.code);
                // formData.append('session_id', uim.session.id);
                // formData.append('app_id', uim.init_param.deploy_app_id);
                // formData.append('init_id', uim.init_id);
                var formData = 'code=' + req.code + '&deploy_app_id=' + uim.init_param.deploy_app_id + '&init_id=' + uim.init_id;
                formData += '&session_id=' + uim.session.id;
                formData += '&data=' + encodeURIComponent(JSON.stringify(req.data));
                xmlhttp.onload = function () {
                    var data;
                    try {
                        data = JSON.parse(this.responseText);
                    } catch (ex) {
                        console.error('接口返回json格式错误', ex.message); // 访问数据接口出错！可能是由于网关超时或服务器端错误，请稍后重试。
                    }
                    if (data)
                        callback(null, data)
                    else
                        callback('json解析错误', this.responseText);
                };
                xmlhttp.onerror = function (err) {
                    uim.api.send();
                    callback('接口请求失败（网络错误）', err);
                    // console.error('接口请求失败（网络错误）')
                }
                xmlhttp.send(formData);
            },
            'uim.socket': function (req, callback) {
                uim.socket.emit('api', req, function (data) {
                    if (typeof data == 'number') {
                        console.log('api执行编号', data)
                        uim.runtime.api_callback[req.index] = function (data) {
                            delete uim.runtime.api_callback[data.index];
                            callback(null, data);
                        };
                    } else {
                        callback(null, data);
                    }
                });
            }
        },
    },
    utils: {

    },

    say_time: function () {
        var mess1;
        var day = new Date()
        var hr = day.getHours()
        if ((hr >= 0) && (hr <= 4))
            mess1 = "夜深了，注意身体！"
        if ((hr >= 4) && (hr < 7))
            mess1 = " 清晨好，早睡早起！"
        if ((hr >= 7) && (hr < 12))
            mess1 = "上午好，一天8杯水"
        if ((hr >= 12) && (hr <= 19))
            mess1 = "下午好，工作顺利！"
        if ((hr >= 19) && (hr <= 23))
            mess1 = "晚上好，别太累了！"
        return mess1
    },
    MD5: function (str) {
        return hex_md5(str);
    },
    dateTime: function (t) {
        if (t === undefined) {
            t = new Date().getTime();
        } else if (typeof t == 'string' && ['+', '-'].indexOf(t.substr(0, 1)) > -1 && isFinite(t)) {
            t = new Date().getTime() + (t * 1000);
        } else if (typeof t == 'number') {
            //
        } else {
            throw 't type error.';
        }
        var nowDate = new Date(t);
        var year = nowDate.getFullYear();
        var month = nowDate.getMonth() + 1 < 10 ? "0" + (nowDate.getMonth() + 1) : nowDate.getMonth() + 1;
        var date = nowDate.getDate() < 10 ? "0" + nowDate.getDate() : nowDate.getDate();
        var hour = nowDate.getHours() < 10 ? "0" + nowDate.getHours() : nowDate.getHours();
        var minute = nowDate.getMinutes() < 10 ? "0" + nowDate.getMinutes() : nowDate.getMinutes();
        var second = nowDate.getSeconds() < 10 ? "0" + nowDate.getSeconds() : nowDate.getSeconds();
        return year + "-" + month + "-" + date + " " + hour + ":" + minute + ":" + second;
    },
    utter: function (e) {
        uim.utterThis = uim.utterThis || (!SpeechSynthesisUtterance ? null : new window.SpeechSynthesisUtterance());
        if (!uim.utterThis) {
            console.warn('浏览器不支持：SpeechSynthesisUtterance');
            return false;
        }
        if (window.speechSynthesis.speaking) {
            window.speechSynthesis.cancel();
        }
        uim.utterThis.rate = e.rate;
        uim.utterThis.text = e.text;
        window.speechSynthesis.speak(uim.utterThis);
        return true;
    },
    url_search: function (url) {
        var e = {};
        if (location.search) {
            var search_arr = (url ? url : location.search).split('?')[1].split('&');
            var tmp_search;
            for (var i = 0; i < search_arr.length; i++) {
                tmp_search = search_arr[i].split('=');
                e[tmp_search[0]] = tmp_search[1];
            }
        }
        return e;
    },
    getStrBT: function (startStr, StrS, endStr, c) {

        // var str1 = StrS.split(startStr);
        // str1.shift();
        // if (str1.length == 0) {
        //     return '';
        // }
        // var str2 = str1.join(startStr).split(endStr);
        // var l = c ? startStr : '';
        // var r = c ? endStr : '';
        // return l + (str2.length == 1 ? '' : str2[0]) + r;

        var lenS, lenE, tmpS, tmpE;
        lenS = startStr.length;
        lenE = endStr.length;
        tmpS = StrS.indexOf(startStr) + lenS;
        if (tmpS == lenS - 1) return '';
        tmpE = StrS.indexOf(endStr, tmpS)
        if (tmpE < 0) return '';
        var l = '', r = '';
        if (c) {
            l = startStr;
            r = endStr;
        }
        return l + StrS.substr(tmpS, tmpE - tmpS) + r;
    },
    array_column: function (arr, col) {
        var tmp_arr = [];
        for (var tmp_i = 0; tmp_i < arr.length; tmp_i++) {
            tmp_arr.push(arr[tmp_i][col]);
        }
        return tmp_arr;
    },
    array_column_kv: function (arr, k, v) {
        // 如果没有传入v则返回整个元素
        var tmp_obj = {};
        for (var tmp_i = 0; tmp_i < arr.length; tmp_i++) {
            tmp_obj['_' + arr[tmp_i][k]] = v ? arr[tmp_i][v] : arr[tmp_i];
        }
        return tmp_obj;
    },
    error_log: {},
    on_error: function (msg, url, line, col, error) {
        console.error('uim catch the error((msg, url, line, col, error):', msg, url, line, col, error);
        if (typeof uim.error_handle.window_error == 'function') {
            uim.error_handle.window_error(msg, url, line, col, error)
        } else if (uim.error_handle.window_error === true) {
            uim.api.emit({
                code: 'uim>deploy.app.error.create/1_0',
                data: {
                    client_log_time: Math.ceil(new Date().getTime() / 1000),
                    msg: msg,
                    url: location.href + ':' + url,
                    line: line,
                    col: col,
                    error_stack: error ? error.stack : 'null',
                    // times: 1,
                },
                success: function (data) {
                    console.info('js错误已上报')
                    if (data.stop) {
                        uim.error_handle.window_error = false;
                        console.info('已停止主动上报js错误信息')
                    }
                },
                error: function (err) {
                    console.warn('上报js错误信息时发生错误', err)
                    uim.error_handle.window_error = false;
                    console.info('已停止主动上报js错误信息')
                    return true;
                }
            })
        } else {
            console.log('未上报js错误信息')
        }
    },
}